package com.yiba.vpn.core;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.VpnService;
import android.os.Build;
import android.os.Handler;
import android.os.ParcelFileDescriptor;

import com.dbq.fast.analysis.EventConstant;
import com.dbq.fast.analysis.YibaAnalysis;
import com.yiba.logutils.LogUtils;
import com.yiba.vpn.R;
import com.yiba.vpn.VPNServiceManager;
import com.yiba.vpn.protocal.DBQTestTimeDelay;
import com.yiba.vpn.protocal.dns.DnsPacket;
import com.yiba.vpn.protocal.tcpip.IPHeader;
import com.yiba.vpn.protocal.tcpip.TCPHeader;
import com.yiba.vpn.protocal.tcpip.UDPHeader;
import com.yiba.vpn.proxy.DnsProxy;
import com.yiba.vpn.proxy.ProxyConfig;
import com.yiba.vpn.proxy.TcpProxyServer;
import com.yiba.vpn.proxy.UdpProxy;
import com.yiba.vpn.utils.CommonMethods;
import com.yiba.vpn.utils.HttpHostHeaderParser;
import com.yiba.vpn.utils.logger.LogUtil;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class LocalVpnService extends VpnService implements Runnable {

    public static final String STATUS_ESTABLISH_VPN_FAILED = "ESTABLISH_VPN_FAILED";

    public static LocalVpnService Instance;
    public static String ProxyUrl;//需要外界传进来

    public static boolean IsRunning = false;

    private static int ID;
    private static int LOCAL_IP;
    private static ConcurrentHashMap<onStatusChangedListener, Object> m_OnStatusChangedListeners
            = new ConcurrentHashMap<>();

    private Thread m_VPNThread;
    private ParcelFileDescriptor m_VPNInterface;
    private TcpProxyServer m_TcpProxyServer;
    private DnsProxy m_DnsProxy;
    private UdpProxy m_UDPProxy;
    private FileOutputStream m_VPNOutputStream;

    private byte[] m_Packet;
    private IPHeader m_IPHeader;
    private TCPHeader m_TCPHeader;
    private UDPHeader m_UDPHeader;
    private ByteBuffer m_DNSBuffer;
    private Handler m_Handler;
    private long m_SentBytes;
    private long m_ReceivedBytes;

    //用于端口测试
    private String server;
    private int port;
    private DBQTestTimeDelay dbqTestTimeDelay;


    public LocalVpnService() {
        ID++;
        m_Handler = new Handler();
        m_Packet = new byte[20000];
        m_IPHeader = new IPHeader(m_Packet, 0);
        //Offset = ip报文头部长度
        m_TCPHeader = new TCPHeader(m_Packet, 20);
        m_UDPHeader = new UDPHeader(m_Packet, 20);
        //Offset = ip报文头部长度 + udp报文头部长度 = 28
        m_DNSBuffer = ((ByteBuffer) ByteBuffer.wrap(m_Packet).position(28)).slice();
        Instance = this;
        if (LogUtil.OPEN_LOG) {
            System.out.printf("New VPNService(%d)\n", ID);
            //LogUtils.getInstance().log(VPNServiceManager.TAG,String.format("New VPNService(%d)\n", ID));
            LogUtils.getInstance().log(VPNServiceManager.TAG, String.format("New VPNService(%d)\n", ID));
        }
        //注入监听对象
        //VpnServiceHelper.onVpnServiceCreated(this);
    }

    @Override
    public void onCreate() {
        if (LogUtil.OPEN_LOG) {
            System.out.printf("VPNService(%s) created.\n", ID);
            LogUtils.getInstance().log(VPNServiceManager.TAG, String.format("VPNService(%s) created.\n", ID));
        }
        // Start a new session by creating a new thread.
        m_VPNThread = new Thread(this, "VPNServiceThread");
        m_VPNThread.start();
        // setVpnRunningStatus(true);
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        IsRunning = true;
        if (intent != null) {
            if (intent.hasExtra("server"))
                server = intent.getStringExtra("server");
            if (intent.hasExtra("port"))
                port = intent.getIntExtra("port", -1);
        }
        return super.onStartCommand(intent, flags, startId);
    }

    //------------------------------VPN状态监听-----------------------------------
    public interface onStatusChangedListener {
        void onStatusChanged(String status, Boolean isRunning);

        void onLogReceived(String logString);
    }

    public static void addOnStatusChangedListener(onStatusChangedListener listener) {
        if (!m_OnStatusChangedListeners.containsKey(listener)) {
            m_OnStatusChangedListeners.put(listener, 1);
        }
    }

    public static void removeOnStatusChangedListener(onStatusChangedListener listener) {
        if (m_OnStatusChangedListeners.containsKey(listener)) {
            m_OnStatusChangedListeners.remove(listener);
        }
    }

    private void onStatusChanged(final String status, final boolean isRunning) {
        m_Handler.post(new Runnable() {
            @Override
            public void run() {
                for (Map.Entry<onStatusChangedListener, Object> entry : m_OnStatusChangedListeners.entrySet()) {
                    entry.getKey().onStatusChanged(status, isRunning);
                }
            }
        });
    }

    public void writeLog(final String format, Object... args) {
        final String logString = String.format(format, args);
        m_Handler.post(new Runnable() {
            @Override
            public void run() {
                for (Map.Entry<onStatusChangedListener, Object> entry : m_OnStatusChangedListeners.entrySet()) {
                    entry.getKey().onLogReceived(logString);
                }
            }
        });
    }

    //------------------------------接受服务器返回的DNS数据报-----------------------------------
    public void sendUDPPacket(IPHeader ipHeader, UDPHeader udpHeader) {
        try {
            CommonMethods.ComputeUDPChecksum(ipHeader, udpHeader);
            this.m_VPNOutputStream.write(ipHeader.m_Data, ipHeader.m_Offset, ipHeader.getTotalLength());
        } catch (IOException e) {
            e.printStackTrace();
            LogUtils.getInstance().log(VPNServiceManager.TAG, String.format("VpnService send UDP packet catch an exception %s", e));
        }
    }

    //---------------------------------配合APP是否可以翻墙-------------------------------------------
    String getAppInstallID() {
        SharedPreferences preferences = getSharedPreferences("DQB_VPN", MODE_PRIVATE);
        String appInstallID = preferences.getString("AppInstallID", null);
        if (appInstallID == null || appInstallID.isEmpty()) {
            appInstallID = UUID.randomUUID().toString();
            Editor editor = preferences.edit();
            editor.putString("AppInstallID", appInstallID);
            editor.apply();
        }
        return appInstallID;
    }

    String getVersionName() {
        try {
            PackageManager packageManager = getPackageManager();
            // getPackageName()是你当前类的包名，0代表是获取版本信息
            PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0);
            String version = packInfo.versionName;
            return version;
        } catch (Exception e) {
            return "0.0";
        }
    }

    @Override
    public synchronized void run() {
        try {
            if (LogUtil.OPEN_LOG) {
                System.out.printf("VPNService(%s) work thread is runing...\n", ID);
                LogUtils.getInstance().log(VPNServiceManager.TAG, String.format("VPNService(%s) work thread is runing...\n", ID));
            }
            ProxyConfig.AppInstallID = getAppInstallID();//获取安装ID
            ProxyConfig.AppVersion = getVersionName();//获取版本号
            if (LogUtil.OPEN_LOG) {
                System.out.printf("AppInstallID: %s\n", ProxyConfig.AppInstallID);
                LogUtils.getInstance().log(VPNServiceManager.TAG, String.format("AppInstallID: %s\n", ProxyConfig.AppInstallID));
            }
            writeLog("Android version: %s", Build.VERSION.RELEASE);
            writeLog("App version: %s", ProxyConfig.AppVersion);

            //ChinaIpMaskManager.loadFromFile(getResources().openRawResource(R.raw.ipmask));//加载中国的IP段，用于IP分流。
            waitUntilPreapred();//检查VPNService是否准备完毕。

            try {
                //ProxyConfig.Instance.loadFromFile(getResources().openRawResource(R.raw.config));//获取Tunnl配置
                writeLog("Load done");
            } catch (Exception e) {
                String errString = e.getMessage();
                if (errString == null || errString.isEmpty()) {
                    errString = e.toString();
                }
                writeLog("Load failed with error: %s", errString);
            }

            m_TcpProxyServer = new TcpProxyServer(0);
            m_TcpProxyServer.start();
            writeLog("LocalTcpServer started.");

            m_DnsProxy = new DnsProxy();
            m_DnsProxy.start();
            writeLog("LocalDnsProxy started.");
            m_UDPProxy = new UdpProxy();

            while (true) {
                if (IsRunning) {
                    //加载配置文件
                    writeLog("set shadowsocks/(http proxy)");
                    try {
                        ProxyConfig.Instance.m_ProxyList.clear();
                        ProxyConfig.Instance.addProxyToList(ProxyUrl);
                        writeLog("Proxy is: %s", ProxyConfig.Instance.getDefaultProxy());
                    } catch (Exception e) {
                        String errString = e.getMessage();
                        if (errString == null || errString.isEmpty()) {
                            errString = e.toString();
                        }
                        LogUtil.e("连接VPN服务失败: " + errString + "proxy is:" + ProxyConfig.Instance.getDefaultProxy());
                        LogUtils.getInstance().log(VPNServiceManager.TAG, "连接VPN服务失败: " + errString + "proxy is:" + ProxyConfig.Instance.getDefaultProxy());
                        IsRunning = false;
                        onStatusChanged(errString, false);
                        continue;
                    }
                    String welcomeInfoString = ProxyConfig.Instance.getWelcomeInfo();
                    if (welcomeInfoString != null && !welcomeInfoString.isEmpty()) {
                        writeLog("%s", ProxyConfig.Instance.getWelcomeInfo());
                    }
                    writeLog("Global mode is " + (ProxyConfig.Instance.globalMode ? "on" : "off"));

                    //增加端口检测，获取端口流量和端口状态
                    if (dbqTestTimeDelay != null) {
                        dbqTestTimeDelay.stop_UDP_TimeDelay();
                        dbqTestTimeDelay = null;
                    }
                    dbqTestTimeDelay = new DBQTestTimeDelay(server, port);
                    dbqTestTimeDelay.start_UDP_TimeDelay();

                    runVPN();
                } else {
                    Thread.sleep(100);
                }
            }
        } catch (InterruptedException e) {
            System.out.println(e);
        } catch (Exception e) {
            e.printStackTrace();
            writeLog("Fatal error: %s", e.toString());
        } finally {
            writeLog("App terminated.");
            dispose();
            if (dbqTestTimeDelay != null)
                dbqTestTimeDelay.stop_UDP_TimeDelay();
        }
    }

    private void checkportFlow() {
        if (dbqTestTimeDelay != null && !DBQTestTimeDelay.IsRunning) {
            LogUtil.e("vpn端口检测失败:");
            LogUtils.getInstance().log(VPNServiceManager.TAG, "vpn端口检测失败: ");
            IsRunning = false;
            onStatusChanged("vpn端口检测失败", false);
        }
    }

    //------------------------------建立VPN，同时监听出口流量-----------------------------------------
    private void runVPN() throws Exception {
        this.m_VPNInterface = establishVPN();
        if (this.m_VPNInterface == null) {
            LogUtil.e("连接VPN服务失败,需要重启手机处理");
            IsRunning = false;
            onStatusChanged(STATUS_ESTABLISH_VPN_FAILED, false);
            return;
        } else {
            onStatusChanged(ProxyConfig.Instance.getSessionName() + getString(R.string.vpn_connected_status), true);
        }
        this.m_VPNOutputStream = new FileOutputStream(m_VPNInterface.getFileDescriptor());
        FileInputStream in = new FileInputStream(m_VPNInterface.getFileDescriptor());
        int size = 0;
        while (size != -1 && IsRunning) {
            while ((size = in.read(m_Packet)) > 0 && IsRunning) {
                if (m_DnsProxy.Stopped || m_TcpProxyServer.Stopped) {
                    in.close();
                    throw new Exception("LocalServer stopped.");
                }
                onIPPacketReceived(m_IPHeader, size);
                checkportFlow();
            }
            Thread.sleep(10);//修改获取数据的延时20-->10
        }
        in.close();
        disconnectVPN();
    }

    void onIPPacketReceived(IPHeader ipHeader, int size) throws IOException {
        switch (ipHeader.getProtocol()) {
            case IPHeader.TCP:
                TCPHeader tcpHeader = m_TCPHeader;
                //矫正TCPHeader里的偏移量，使它指向真正的TCP数据地址
                tcpHeader.m_Offset = ipHeader.getHeaderLength();
                if (ipHeader.getSourceIP() == LOCAL_IP) {
                    if (tcpHeader.getSourcePort() == m_TcpProxyServer.Port) {// 收到本地TCP服务器数据
                        NatSession session = NatSessionManager.getSession(tcpHeader.getDestinationPort());
                        if (session != null) {
                            ipHeader.setSourceIP(ipHeader.getDestinationIP());
                            tcpHeader.setSourcePort(session.RemotePort);
                            ipHeader.setDestinationIP(LOCAL_IP);

                            CommonMethods.ComputeTCPChecksum(ipHeader, tcpHeader);
                            m_VPNOutputStream.write(ipHeader.m_Data, ipHeader.m_Offset, size);
                            m_ReceivedBytes += size;
                        } else {
                            if (LogUtil.OPEN_LOG) {
                                LogUtils.getInstance().log(VPNServiceManager.TAG, String.format("NoSession: %s %s\n", ipHeader.toString(), tcpHeader.toString()));
                                System.out.printf("NoSession: %s %s\n", ipHeader.toString(), tcpHeader.toString());
                            }
                        }
                    } else {

                        // 添加端口映射
                        int portKey = tcpHeader.getSourcePort();
                        NatSession session = NatSessionManager.getSession(portKey);
                        if (session == null || session.RemoteIP != ipHeader.getDestinationIP() || session.RemotePort != tcpHeader.getDestinationPort()) {
                            session = NatSessionManager.createSession(portKey, ipHeader.getDestinationIP(), tcpHeader.getDestinationPort());
                        }

                        session.LastNanoTime = System.nanoTime();
                        session.PacketSent++;//注意顺序

                        int tcpDataSize = ipHeader.getDataLength() - tcpHeader.getHeaderLength();
                        if (session.PacketSent == 2 && tcpDataSize == 0) {
                            return;//丢弃tcp握手的第二个ACK报文。因为客户端发数据的时候也会带上ACK，这样可以在服务器Accept之前分析出HOST信息。
                        }

                        //分析数据，找到host
                        if (session.BytesSent == 0 && tcpDataSize > 10) {
                            int dataOffset = tcpHeader.m_Offset + tcpHeader.getHeaderLength();
                            String host = HttpHostHeaderParser.parseHost(tcpHeader.m_Data, dataOffset, tcpDataSize);
                            if (host != null) {
                                session.RemoteHost = host;

                            } else {
                                if (LogUtil.OPEN_LOG) {
                                    LogUtils.getInstance().log(VPNServiceManager.TAG, String.format("No host name found: %s", session.RemoteHost));
                                    System.out.printf("No host name found: %s", session.RemoteHost);
                                }
                            }
                        }

                        // 转发给本地TCP服务器
                        ipHeader.setSourceIP(ipHeader.getDestinationIP());
                        ipHeader.setDestinationIP(LOCAL_IP);
                        tcpHeader.setDestinationPort(m_TcpProxyServer.Port);
                        if (LogUtil.OPEN_LOG) {
                            LogUtils.getInstance().log(VPNServiceManager.TAG, String.format("获取代理TCP流: %s %s %s\n", session.RemoteHost, ipHeader.toString(), tcpHeader.toString()));
                            System.out.printf("获取代理TCP流: %s %s %s\n", session.RemoteHost, ipHeader.toString(), tcpHeader.toString());
                        }
                        CommonMethods.ComputeTCPChecksum(ipHeader, tcpHeader);
                        m_VPNOutputStream.write(ipHeader.m_Data, ipHeader.m_Offset, size);
                        session.BytesSent += tcpDataSize;//注意顺序
                        m_SentBytes += size;
                    }
                }
                break;
            case IPHeader.UDP:
                // 转发DNS数据包：
                UDPHeader udpHeader = m_UDPHeader;
                udpHeader.m_Offset = ipHeader.getHeaderLength();
                if (ipHeader.getSourceIP() == LOCAL_IP && udpHeader.getDestinationPort() == 53) {
                    LogUtil.e("转发DNS数据包");
                    LogUtils.getInstance().log(VPNServiceManager.TAG, "转发DNS数据包");
                    m_DNSBuffer.clear();
                    m_DNSBuffer.limit(ipHeader.getDataLength() - 8);
                    DnsPacket dnsPacket = DnsPacket.FromBytes(m_DNSBuffer);
                    if (dnsPacket != null && dnsPacket.Header.QuestionCount > 0) {
                        LogUtils.getInstance().log(VPNServiceManager.TAG, String.format("let the DnsProxy to process DNS request...\n"));
                        LogUtils.getInstance().log(VPNServiceManager.TAG, String.format("DNS", "Query " + dnsPacket.Questions[0].Domain));
                        m_DnsProxy.onDnsRequestReceived(ipHeader, udpHeader, dnsPacket);
                    }
                } else if (ipHeader.getSourceIP() == LOCAL_IP) {
                    LogUtil.e("转发UDP数据包");
                    LogUtils.getInstance().log(VPNServiceManager.TAG, "转发udp数据包");
                    m_UDPProxy.onUDPRequestReceivedHandler(ipHeader, udpHeader, udpHeader.getTotalLength() - 8);
                }
                break;
        }
    }

    private void waitUntilPreapred() {
        while (prepare(this) != null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private ParcelFileDescriptor establishVPN() throws Exception {
        Builder builder = new Builder();
        builder.setMtu(ProxyConfig.Instance.getMTU());
        if (LogUtil.OPEN_LOG)
            System.out.printf("setMtu: %d\n", ProxyConfig.Instance.getMTU());

        ProxyConfig.IPAddress ipAddress = ProxyConfig.Instance.getDefaultLocalIP();
        LOCAL_IP = CommonMethods.ipStringToInt(ipAddress.Address);
        builder.addAddress(ipAddress.Address, ipAddress.PrefixLength);
        if (LogUtil.OPEN_LOG)
            System.out.printf("addAddress: %s/%d\n", ipAddress.Address, ipAddress.PrefixLength);

        for (ProxyConfig.IPAddress dns : ProxyConfig.Instance.getDnsList()) {
            builder.addDnsServer(dns.Address);
            if (LogUtil.OPEN_LOG)
                System.out.printf("addDnsServer: %s\n", dns.Address);
        }

        if (ProxyConfig.Instance.getRouteList().size() > 0) {
            for (ProxyConfig.IPAddress routeAddress : ProxyConfig.Instance.getRouteList()) {
                builder.addRoute(routeAddress.Address, routeAddress.PrefixLength);
                if (LogUtil.OPEN_LOG)
                    System.out.printf("addRoute: %s/%d\n", routeAddress.Address, routeAddress.PrefixLength);
            }
            builder.addRoute(CommonMethods.ipIntToString(ProxyConfig.FAKE_NETWORK_IP), 16);

            if (LogUtil.OPEN_LOG)
                System.out.printf("addRoute for FAKE_NETWORK: %s/%d\n", CommonMethods.ipIntToString(ProxyConfig.FAKE_NETWORK_IP), 16);
        } else {
            builder.addRoute("0.0.0.0", 0);
            if (LogUtil.OPEN_LOG)
                System.out.printf("addDefaultRoute: 0.0.0.0/0\n");
        }

        Class<?> SystemProperties = Class.forName("android.os.SystemProperties");
        Method method = SystemProperties.getMethod("get", new Class[]{String.class});
        ArrayList<String> servers = new ArrayList<String>();
        //解决Android O 缺失net.dns1的问题
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
            ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
            Network network = connectivityManager.getActiveNetwork();
            LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
            if (linkProperties != null) {
                for (InetAddress address : linkProperties.getDnsServers())
                    servers.add(address.getHostAddress());
            }
        } else {
            for (String name : new String[]{"net.dns1", "net.dns2", "net.dns3", "net.dns4",}) {
                String value = (String) method.invoke(null, name);
                if (value != null && !"".equals(value) && !servers.contains(value)) {
                    servers.add(value);
                    if (value.replaceAll("\\d", "").length() == 3) {//防止IPv6地址导致问题
                        builder.addRoute(value, 32);
                    } else {
                        builder.addRoute(value, 128);
                    }
                    if (LogUtil.OPEN_LOG)
                        System.out.printf("%s=%s\n", name, value);
                }
            }
        }
//        if (AppProxyManager.isLollipopOrAbove) {
//            if (AppProxyManager.Instance.proxyAppInfo.size() == 0) {
//                writeLog("Proxy All Apps");
//            }
//            for (AppProxyManager.AppInfo app : AppProxyManager.Instance.proxyAppInfo){
//                builder.addAllowedApplication("com.dbq.fast");//需要把自己加入代理，不然会无法进行网络连接
//                try{
//                    builder.addAllowedApplication(app.getPkgName());
//                    writeLog("Proxy App: " + app.getAppLabel());
//                } catch (Exception e){
//                    e.printStackTrace();
//                    writeLog("Proxy App Fail: " + app.getAppLabel());
//                }
//            }
//        } else {
//            writeLog("No Pre-App proxy, due to low Android version.");
//        }
        Intent intent = new Intent();
        intent.setPackage(getPackageName());
        intent.setAction(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
//        builder.setConfigureIntent(pendingIntent);
        builder.setSession(getString(R.string.unicorn));
        ParcelFileDescriptor pfdDescriptor = builder.establish();
        return pfdDescriptor;
    }

    public void disconnectVPN() {
        try {
            if (m_VPNInterface != null) {
                m_VPNInterface.close();
                m_VPNInterface = null;
            }
        } catch (Exception e) {
            // ignore
        }
        onStatusChanged(ProxyConfig.Instance.getSessionName() + getString(R.string.vpn_disconnected_status), false);
        this.m_VPNOutputStream = null;
    }

    private synchronized void dispose() {
        // 断开VPN
        disconnectVPN();

        // 停止TcpServer
        if (m_TcpProxyServer != null) {
            m_TcpProxyServer.stop();
            m_TcpProxyServer = null;
            writeLog("LocalTcpServer stopped.");
        }

        // 停止DNS解析器
        if (m_DnsProxy != null) {
            m_DnsProxy.stop();
            m_DnsProxy = null;
            writeLog("LocalDnsProxy stopped.");
        }

        // 停止端口检测功能
        if (dbqTestTimeDelay != null) {
            dbqTestTimeDelay.stop_UDP_TimeDelay();
            dbqTestTimeDelay = null;
            writeLog("DBQTestTimeDelayProxy stopped.");
        }

        stopSelf();
        IsRunning = false;
        System.exit(0);
    }

    @Override
    public void onDestroy() {
        YibaAnalysis.getInstance().event(getApplicationContext(), EventConstant.EVENT_CLOSE_SUCC_NOTI);
        if (LogUtil.OPEN_LOG)
            System.out.printf("VPNService(%s) destoried.\n", ID);
        LogUtils.getInstance().log(VPNServiceManager.TAG, String.format("VPNService(%s) destoried.\n", ID));
        if (m_VPNThread != null) {
            m_VPNThread.interrupt();
        }
    }

//    public boolean vpnRunningStatus() {
//        return IsRunning;
//    }
//
//    public void setVpnRunningStatus(boolean isRunning) {
//        IsRunning = isRunning;
//    }

}
